package com.yykj.hospital.server.service.bank.impl;

import com.yykj.bank.client.dto.BankInfoDTO;
import com.yykj.bank.client.dto.BankVerifyDTO;
import com.yykj.bank.client.feign.BankFeignClient;
import com.yykj.bank.client.vo.BankVerifyResultVO;
import com.yykj.common.annotation.FrequencyLimit;
import com.yykj.common.constant.CommonConstant;
import com.yykj.common.constant.RedisConstant;
import com.yykj.common.enums.ResultEnums;
import com.yykj.common.exception.NormalException;
import com.yykj.common.service.RedisService;
import com.yykj.common.util.LocalStringUtil;
import com.yykj.common.util.PageRequestUtil;
import com.yykj.common.vo.PageData;
import com.yykj.hospital.client.dto.HospitalSettingDTO;
import com.yykj.hospital.client.dto.bank.DoctorBankCashApplyDTO;
import com.yykj.hospital.server.dto.account.ExpendDTO;
import com.yykj.hospital.server.dto.bank.BankListDTO;
import com.yykj.hospital.server.dto.bank.DoctorCashFeeTypeDTO;
import com.yykj.hospital.server.enums.AccountLogType;
import com.yykj.hospital.server.form.CashApplyForm;
import com.yykj.hospital.server.form.bank.BindCardInfoForm;
import com.yykj.hospital.server.form.bank.DoctorCashApplySearchForm;
import com.yykj.hospital.server.model.entity.DoctorPassword;
import com.yykj.hospital.server.model.entity.HospitalDoctor;
import com.yykj.hospital.server.model.entity.bank.DoctorBankCard;
import com.yykj.hospital.server.model.entity.bank.DoctorBankCashApply;
import com.yykj.hospital.server.model.repository.DoctorPasswordRepository;
import com.yykj.hospital.server.model.repository.HospitalDoctorRepository;
import com.yykj.hospital.server.model.repository.bank.DoctorBankCardRepository;
import com.yykj.hospital.server.model.repository.bank.DoctorBankCashApplyRepository;
import com.yykj.hospital.server.service.HospitalService;
import com.yykj.hospital.server.service.account.AccountService;
import com.yykj.hospital.server.service.bank.BankService;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import javax.transaction.Transactional;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;

@Service
public class BankServiceImpl implements BankService {

    @Override
    public void bindCardToDoctor(BindCardInfoForm infoForm) {
        if (StringUtils.isEmpty(infoForm.getDoctorId()) || StringUtils.isEmpty(infoForm.getCardNo())) {
            throw new NormalException(ResultEnums.PARAM_ERROR);
        }
        //检查银行卡是否已经绑定了
        DoctorBankCard doctorBankCard = doctorBankCardRepository.findFirstByDoctorIdAndAccountNo(infoForm.getDoctorId(), infoForm.getCardNo());
        if (doctorBankCard != null) {
            throw new NormalException(ResultEnums.BANK_CARD_ALREADY_BIND_ERROR);
        }
        BankVerifyDTO dto = new BankVerifyDTO();
        dto.setIdCard(infoForm.getIdCardNo());
        dto.setAccountNo(infoForm.getCardNo());
        dto.setMobile(infoForm.getMobile());
        dto.setName(infoForm.getDoctorName());
        //身份证检查
        HospitalDoctor doctor = doctorRepository.findById(infoForm.getDoctorId()).orElseThrow(() -> new NormalException(ResultEnums.NOT_EXIST));
        if (StringUtils.isEmpty(dto.getIdCard())) {
            if (StringUtils.isEmpty(doctor.getIdCardNo())) {
                throw new NormalException(ResultEnums.DOCTOR_ID_CARD_NO_NOT_EXIST);
            }
            dto.setIdCard(doctor.getIdCardNo());
        }
        //姓名检查
        if (StringUtils.isEmpty(dto.getName())) {
            dto.setName(doctor.getDoctorName());
        }
        //手机号检查
        if (StringUtils.isEmpty(dto.getMobile())) {
            dto.setMobile(doctor.getMobile());
        }
        //银行卡号
        BankVerifyResultVO verify = bankFeignClient.verify(dto);

        if (verify.getCode() == 0) {
            if (!"借记卡".equals(verify.getCardType())) {
                throw new NormalException(ResultEnums.BANK_CARD_TYPE_ERROR);
            }
            if (!StringUtils.isEmpty(infoForm.getBank()) && !verify.getBank().equals(infoForm.getBank())) {
                throw new NormalException(ResultEnums.BANK_NOT_MATCH_ERROR);
            }
            doctorBankCard = new DoctorBankCard();
            BeanUtils.copyProperties(verify, doctorBankCard);
            doctorBankCard.setDoctorId(doctor.getDoctorId());
            doctorBankCardRepository.save(doctorBankCard);
        } else {
            throw new NormalException(ResultEnums.DOCTOR_BANK_CARD_BIND_ERROR, verify.getMsg());
        }

    }

    @Override
    public List<BankListDTO> listDoctorBankCard(String doctorId) {
        List<DoctorBankCard> list = doctorBankCardRepository.findAllByDoctorId(doctorId);
        List<BankListDTO> result = new ArrayList<>();
        List<BankInfoDTO> bankInfoList = bankFeignClient.listAvailableBank();
        Map<String, BankInfoDTO> bankInfoDTOMap = new HashMap<>();
        for (BankInfoDTO infoDTO : bankInfoList) {
            bankInfoDTOMap.put(infoDTO.getName(), infoDTO);
        }
        for (DoctorBankCard doctorBankCard : list) {
            BankListDTO dto = new BankListDTO();
            BeanUtils.copyProperties(doctorBankCard, dto);
            BankInfoDTO infoDTO = bankInfoDTOMap.get(dto.getBank());
            if (infoDTO != null) {
                BeanUtils.copyProperties(infoDTO,dto);
            }
            result.add(dto);
        }

        return result;
    }

    @Override
    public void deleteBindCard(Integer id) {
        doctorBankCardRepository.deleteById(id);
    }
    @Override
    public void checkPayPasswordCnt(DoctorPassword doctorPassword, String password) {
        String key = "doctor_pay_password_" + doctorPassword.getDoctorId();
        String failCnt = redisService.get(key);
        int cnt = 0;
        if (failCnt != null) {
            cnt = Integer.parseInt(failCnt);
        }
        if (cnt >= CommonConstant.PAY_PASSWORD_MAX_FAIL_CNT) {
            throw new NormalException(ResultEnums.PAY_PASSWORD_MAX_FAIL_CNT_ERROR);
        }

        if (!doctorPassword.getPayPwd().equals(LocalStringUtil.encrypt(password))) {//密码检查不正确
            cnt++;
            int remain = CommonConstant.PAY_PASSWORD_MAX_FAIL_CNT - cnt;
            redisService.set(key, "" + cnt, RedisConstant.EXPIRE_DAY);
            if (remain > 0) {
                throw new NormalException(ResultEnums.PASSWORD_ERROR, "密码不正确：还剩" + remain + "次");
            } else {
                throw new NormalException(ResultEnums.PASSWORD_ERROR, "密码不正确，已锁定24小时");
            }
        } else {
            redisService.set(key, "0");
        }
    }

    @Override
    @Transactional
    @FrequencyLimit
    public void cashToBankApply(CashApplyForm form) {

        //1.支付密码是否设置了
        DoctorPassword doctorPassword = doctorPasswordRepository.findById(form.getDoctorId()).orElse(null);
        if (doctorPassword == null) {
            throw new NormalException(ResultEnums.PAY_PASSWORD_NOT_SET_ERROR);
        }
        // 密码次数限制
        checkPayPasswordCnt(doctorPassword, form.getPassword());

        //2.银行卡是否绑定了
        DoctorBankCard doctorBankCard = doctorBankCardRepository.findFirstByDoctorIdAndAccountNo(form.getDoctorId(), form.getBankCardNo());
        if (doctorBankCard == null) {
            throw new NormalException(ResultEnums.BANK_CARD_NOT_BIND_ERROR);
        }
        //3.最小提现额，最大提现额检查
        HospitalSettingDTO hospitalSetting = hospitalService.getHospitalSetting(form.getHospitalId());
        if (form.getTotal().compareTo(hospitalSetting.getMinCashPrice()) < 0 && form.getTotal().compareTo(hospitalSetting.getMaxCashPrice()) > 0) {
            throw new NormalException(ResultEnums.CASH_AMOUNT_ERROR);
        }
        //4.价格是否计算正确
        BigDecimal cashFeePrice = BigDecimal.ZERO;
        if (hospitalSetting.getCashFeeType() != null) {
            switch (hospitalSetting.getCashFeeType()) {
                case fixed:
                    cashFeePrice = hospitalSetting.getCashPrice();
                    break;
                case percent:
                    cashFeePrice = form.getTotal().multiply(hospitalSetting.getCashPrice()).setScale(2, RoundingMode.HALF_UP);
                    break;
                default:
                    throw new NormalException(ResultEnums.CASH_FEE_SET_ERROR);
            }
        }
        if (cashFeePrice.compareTo(form.getApplyFee()) != 0) {
            throw new NormalException(ResultEnums.CASH_FEE_CALC_ERROR);
        }


        //5.查检当日次数是否超出限制
        String cashApplyKey = "cash_apply_" + form.getDoctorId() + LocalDate.now().toString();
        int cnt;
        String s = redisService.get(cashApplyKey);
        if (s == null) {
            cnt = 0;
        } else {
            cnt = Integer.parseInt(s);
        }
        cnt++;
        redisService.set(cashApplyKey, "" + cnt, RedisConstant.EXPIRE_DAY);

        HospitalDoctor doctor = doctorRepository.findById(form.getDoctorId()).orElseThrow(() -> new NormalException(ResultEnums.NOT_EXIST));

        String orderNo = LocalStringUtil.getOrderNum();
        DoctorBankCashApply doctorBankCashApply = DoctorBankCashApply.builder()
                .orderNo(orderNo)
                .hospitalId(doctor.getHospitalId())
                .doctorId(form.getDoctorId())
                .doctorName(doctor.getDoctorName())
                .total(form.getTotal())
                .applyFee(form.getApplyFee())
                .amount(form.getAmount())
                .bankCardNo(form.getBankCardNo())
                .status(0)
                .remark("提现申请")
                .build();

        applyRepository.save(doctorBankCashApply);

        ExpendDTO expendDTO = ExpendDTO.builder()
                .hospitalId(form.getHospitalId())
                .doctorId(form.getDoctorId())
                .orderNum(orderNo)
                .logType(AccountLogType.cash_out)
                .price(form.getTotal())
                .detail("提现申请")
                .build();
        accountService.doctorCashOutApply(expendDTO);
    }

    @Override
    @Transactional
    public void passDoctorCashApply(String orderNo, String remark) {
        DoctorBankCashApply apply = applyRepository.findByOrderNo(orderNo);
        if (apply == null) {
            throw new NormalException(ResultEnums.NOT_EXIST);
        }
        if (apply.getStatus() == 0) {
            apply.setStatus(1);
            apply.setRemark(remark);
            apply.setFinisTime(LocalDateTime.now());
            applyRepository.save(apply);
            accountService.doctorCashOutPass(apply);
        }
    }

    @Override
    @Transactional
    public void refuseDoctorCashApply(String orderNo, String remark) {
        DoctorBankCashApply apply = applyRepository.findByOrderNo(orderNo);
        if (apply == null) {
            throw new NormalException(ResultEnums.NOT_EXIST);
        }
        apply.setStatus(2);
        apply.setRemark(remark);
        apply.setFinisTime(LocalDateTime.now());
        applyRepository.save(apply);
        accountService.doctorCashOutRefused(apply);
    }

    @Override
    public PageData<DoctorBankCashApplyDTO> listDoctorCashApply(DoctorCashApplySearchForm form) {
        PageRequest pageRequest = PageRequest.of(PageRequestUtil.getPage(form.getCurrentPage()), PageRequestUtil.getSize(form.getPageSize())
                , Sort.by(Arrays.asList(Sort.Order.desc("addTime"), Sort.Order.asc("status"))));
        Page<DoctorBankCashApply> list = applyRepository.findAll(generateSpecification(form), pageRequest);
        PageData<DoctorBankCashApply> applyPageData = PageData.formatList(list, form.getCurrentPage(), form.getPageSize());
        PageData<DoctorBankCashApplyDTO> ret = new PageData<>();
        List<DoctorBankCashApplyDTO> retList = new ArrayList<>();
        ret.setTotalCount(applyPageData.getTotalCount());
        ret.setTotalPage(applyPageData.getTotalPage());
        ret.setPageSize(applyPageData.getPageSize());
        ret.setCurrentPage(applyPageData.getCurrentPage());
        for (DoctorBankCashApply apply : applyPageData.getList()) {
            DoctorBankCashApplyDTO dto = new DoctorBankCashApplyDTO();
            BeanUtils.copyProperties(apply, dto);
            retList.add(dto);
        }
        ret.setList(retList);
        return ret;
    }

    @Override
    public DoctorCashFeeTypeDTO cashFeeType(String doctorId) {
        HospitalDoctor doctor = doctorRepository.findById(doctorId).orElseThrow(() -> new NormalException(ResultEnums.NOT_EXIST));
        HospitalSettingDTO hospitalSetting = hospitalService.getHospitalSetting(doctor.getHospitalId());
        DoctorCashFeeTypeDTO ret = new DoctorCashFeeTypeDTO();
        ret.setDoctorId(doctorId);
        ret.setType(hospitalSetting.getCashFeeType());
        ret.setValue(hospitalSetting.getCashPrice());
        return ret;
    }

    private Specification<DoctorBankCashApply> generateSpecification(DoctorCashApplySearchForm form) {
        return new Specification<DoctorBankCashApply>() {
            @Override
            public Predicate toPredicate(Root<DoctorBankCashApply> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
                List<Predicate> predicateList = new ArrayList<>();
                if (form.getHospitalId() != null && form.getHospitalId() > 0) {
                    predicateList.add(builder.equal(root.get("hospitalId"), form.getHospitalId()));
                }
                if (!StringUtils.isEmpty(form.getOrderNo())) {
                    predicateList.add(builder.equal(root.get("orderNo"), form.getDoctorId()));
                }

                if (!StringUtils.isEmpty(form.getDoctorName())) {
                    predicateList.add(builder.like(root.get("doctorName"), "%" + form.getDoctorName() + "%"));
                }
                if (!StringUtils.isEmpty(form.getDoctorId())) {
                    predicateList.add(builder.equal(root.get("doctorId"), form.getDoctorId()));
                }
                if (!StringUtils.isEmpty(form.getBankCardNo())) {
                    predicateList.add(builder.equal(root.get("bankCardNo"), form.getBankCardNo()));
                }
                if (form.getStatus() != null) {
                    predicateList.add(builder.equal(root.get("status"), form.getStatus()));
                }
                if (!StringUtils.isEmpty(form.getStartTime())) {
                    LocalDateTime startTime = LocalDateTime.parse(form.getStartTime(), DateTimeFormatter.ofPattern("yyyy-MM-dd"));
                    predicateList.add(builder.greaterThanOrEqualTo(root.get("addTime"), startTime));
                }
                if (!StringUtils.isEmpty(form.getEndTime())) {
                    LocalDateTime endTime = LocalDateTime.parse(form.getEndTime(), DateTimeFormatter.ofPattern("yyyy-MM-dd"));
                    endTime = endTime.plusDays(1);
                    predicateList.add(builder.lessThan(root.get("addTime"), endTime));
                }

                Predicate[] predicates = new Predicate[predicateList.size()];
                return query.where(predicateList.toArray(predicates)).getRestriction();
            }
        };
    }

    private RedisService redisService;
    private BankFeignClient bankFeignClient;

    private DoctorBankCardRepository doctorBankCardRepository;
    private DoctorBankCashApplyRepository applyRepository;

    private HospitalService hospitalService;

    private AccountService accountService;

    private HospitalDoctorRepository doctorRepository;

    private DoctorPasswordRepository doctorPasswordRepository;

    @Autowired
    public void setDoctorBankCardRepository(DoctorBankCardRepository doctorBankCardRepository) {
        this.doctorBankCardRepository = doctorBankCardRepository;
    }


    @Autowired
    public void setHospitalService(HospitalService hospitalService) {
        this.hospitalService = hospitalService;
    }

    @Autowired
    public void setAccountService(AccountService accountService) {
        this.accountService = accountService;
    }

    @Autowired
    public void setApplyRepository(DoctorBankCashApplyRepository applyRepository) {
        this.applyRepository = applyRepository;
    }

    @Autowired
    public void setRedisService(RedisService redisService) {
        this.redisService = redisService;
    }

    @Autowired
    public void setDoctorRepository(HospitalDoctorRepository doctorRepository) {
        this.doctorRepository = doctorRepository;
    }

    @Autowired
    public void setBankFeignClient(BankFeignClient bankFeignClient) {
        this.bankFeignClient = bankFeignClient;
    }

    @Autowired
    public void setDoctorPasswordRepository(DoctorPasswordRepository doctorPasswordRepository) {
        this.doctorPasswordRepository = doctorPasswordRepository;
    }
}
